package cn.akwangl.concurrency.web.common.lock;

import cn.akwangl.concurrency.web.exception.BizException;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * ZK 分布式锁
 *
 * @author 阿坤同学
 * @date 2022-11-24 14:00
 */
@Slf4j
public class ZkLock implements AutoCloseable, Watcher {

    private ZooKeeper zooKeeper;

    private String zNode;

    public ZkLock() throws IOException {
        this.zooKeeper = new ZooKeeper("123.249.18.14:2181", 1000, this);
        log.info("zookeeper 连接成功");
    }

    /**
     * 获取分布式锁,当前锁默认过期时间为5s.
     *
     * @param businessCode 业务code
     * @return true:获取锁成功 false:获取锁失败
     * @author 阿坤同学
     * @date 2022-11-24 21:10
     */
    public boolean getLock(String businessCode) throws InterruptedException, KeeperException {
        String rootNode = "/" + businessCode;
        Stat stat = zooKeeper.exists(rootNode, false);
        if (stat == null) {
            try {
                zooKeeper.create(rootNode, businessCode.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            } catch (Exception e) {
                stat = zooKeeper.exists(rootNode, false);
                if (stat == null) {
                    throw new BizException(String.format("zk %s 节点不存在,获取锁失败", businessCode));
                }
            }
        }
        // 创建瞬时有序节点 过期时间为10s
        zNode = zooKeeper.create(rootNode + "/" + businessCode + "_", businessCode.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT_SEQUENTIAL_WITH_TTL, stat, 50000);

        List<String> childrenNodes = zooKeeper.getChildren(rootNode, false);
        Collections.sort(childrenNodes);
        String firstNode = childrenNodes.get(0);
        if (zNode.endsWith(firstNode)) {
            return true;
        }
        //不是第一个子节点，则监听前一个节点
        String lastNode = firstNode;
        for (String node : childrenNodes) {
            if (zNode.endsWith(node)) {
                zooKeeper.exists(rootNode + "/" + lastNode, true);
                break;
            } else {
                lastNode = node;
            }
        }
        synchronized (this) {
            wait();
        }
        return true;
    }

    @Override
    public void process(WatchedEvent watchedEvent) {
        if (watchedEvent.getType() == Event.EventType.NodeDeleted) {
            synchronized (this) {
                notify();
            }
        }
    }

    @Override
    public void close() throws Exception {
        zooKeeper.delete(zNode, -1);
        log.debug("zk:{}节点已被清除", zNode);
    }
}
